%%--- coding:utf-8 ---
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% File Name: mc_worker
%%% Created on : 2024/4/16 20:41
%%% @author Gaylen 252323463@qq.com
%%% @copyright (C) 2024, freedom
%%% @doc
%%%
%%% @end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-module(mc_worker).
-behaviour(gen_server).
-author("Gaylen").
-include("mongodb_driver.hrl").

-define(LOG_DEFAULT_DB, fun() -> error_logger:info_msg("Using default 'test' database"), <<"test">> end).


%% API
-export([start_link/1, disconnect/1]).
-export([
    init/1,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3
]).

%% Options
%%   host 数据库ip,默认"127.0.0.1"
%%   port 数据库端口,默认27017
%%   database 访问的数据库名,默认<<"test">>
%%   user 用户名
%%   password 密码
%%   auth_source 授权源，一般为 <<"admin">>
%%   ssl  是否使用ssl连接, true|false
%%   ssl_opts SSL参数
%%   timeout 连接超时,单位毫秒,默认infinity
%%   register 注册进程别名
-spec start_link(proplists:proplist()) -> {ok, pid()}.
start_link(Options) ->
    proc_lib:start_link(?MODULE, init, [Options]).

init(Options) ->
    erlang:process_flag(trap_exit, true),
    case mc_worker_logic:connect(Options) of
        {ok, Socket} ->
            priv_try_register(Options),
%%            ConnState = priv_init_conn_state(Options),
            NetModule = priv_get_net_module(Options),
            %% 启动60秒定时器，定时ping数据库保持心跳连接
            TimerRef = erlang:send_after(60000, self(), {ping}),
            WorkerState0 = #mongo_worker_state{
%%                conn_state = ConnState,
                net_module = NetModule,
                socket = Socket,
%%                is_master = false,
                alive_tick = erlang:system_time(seconds),
                alive_timer = TimerRef
            },
            WorkerState = mc_worker_logic:ping(WorkerState0),
            proc_lib:init_ack({ok, self()}),
            gen_server:enter_loop(?MODULE, [], WorkerState);
        Error ->
            proc_lib:init_ack(Error)
    end.

%%%% @private
%%priv_init_conn_state(Options) ->
%%    Database = mc_utils:get_value(database, Options, <<"test">>),
%%    RMode = mc_utils:get_value(r_mode, Options, master),
%%    WMode = mc_utils:get_value(w_mode, Options, unsafe),
%%    #conn_state{database = Database, read_mode = RMode, write_mode = WMode}.

%% ssl 连接方式还是tcp连接方式
%% @private
priv_get_net_module(Options) ->
    case mc_utils:get_value(ssl, Options, false) of
        true -> ssl;
        false -> gen_tcp
    end.

%% 注册数据库进程的进程别名
%% @private
priv_try_register(Options) ->
    case lists:keyfind(register, 1, Options) of
        false -> ok;
        {_, Name} when is_atom(Name) -> register(Name, self());
        {_, RegFun} when is_function(RegFun) -> RegFun(self())
    end.

%% halt worker, close tcp connection
disconnect(Worker) ->
    gen_server:cast(Worker, halt).

handle_call({opmsg, RequestId, OpQueryBin}, From, State) ->
    NewState = mc_worker_logic:process_request(RequestId, OpQueryBin, From, State),
    {noreply, NewState};
handle_call({query, RequestId, OpQueryBin}, From, State) ->
    NewState = mc_worker_logic:process_request(RequestId, OpQueryBin, From, State),
    {noreply, NewState};
handle_call({stop, _}, _From, State) -> % stop request
    {stop, normal, ok, State}.

handle_cast({opmsg, RequestId, OpQueryBin, MongoOpType, CallBackPid, CallBackFunc, CallBackArgs}, State) ->
    NewState = mc_worker_logic:process_request(RequestId, OpQueryBin, CallBackPid, State, ?MONGO_REQ_ASYNC, MongoOpType, CallBackFunc, CallBackArgs),
    {noreply, NewState};
handle_cast({query, RequestId, OpQueryBin}, State) ->
    NewState = mc_worker_logic:process_request(RequestId, OpQueryBin, undefined, State),
    {noreply, NewState};
handle_cast(halt, State) ->
    {stop, normal, State};
handle_cast(_, State) ->
    {noreply, State}.

%% @hidden
handle_info({ping}, State) ->
    TimerRef = erlang:send_after(60000, self(), {ping}),
    NewState0 = mc_worker_logic:ping(State#mongo_worker_state{alive_tick = TimerRef}),
    %% 检查心跳时间是否超时，是则认为连接已断开, 停止mc_worker进程，由父进程重新建立连接
    NewState = mc_worker_logic:check_alive(NewState0),
    {noreply, NewState};
handle_info({Net, _Socket, Data}, State) when Net =:= tcp; Net =:= ssl ->
    Buffer = <<(State#mongo_worker_state.buffer)/binary, Data/binary>>,
    NewState = mc_worker_logic:unpack_buffer(Buffer, State),
    {noreply, NewState};
handle_info({NetR, _Socket}, State) when NetR =:= tcp_closed; NetR =:= ssl_closed ->
    {stop, tcp_closed, State};
%%handle_info(hibernate, State) ->
%%    {noreply, State#mongo_worker_state{hibernate_timer = undefined}, hibernate};
handle_info({NetR, _Socket, Reason}, State) when NetR =:= tcp_error; NetR =:= ssl_error ->
    {stop, Reason, State}.

%% @hidden
terminate(Reason, State = #mongo_worker_state{net_module = NetModule}) ->
    try
        NetModule:close(State#mongo_worker_state.socket)
    catch
        _:_ ->
            ok
    end,
    {stop, Reason, State}.

%% @hidden
code_change(_Old, State, _Extra) ->
    {ok, State}.